'''
Created on 28 sept. 2010
@author: Marc CARRE
@contact: carre.marc@gmail.com
@license: GNU GPLv3
@version: 1.0
'''

import sys
import getopt
import os
from DataSetGenerator import DataSetGenerator

class ConsoleLauncher:
    
    def __init__(self):
        self.gotError = False
    
    def launch(self, argv):
        self.gotError = False
        params = None 
        spaces = None
        limits = None
        numPoints = None
        outFile = None
        intValues = None
        
        try:         
            shortList = "p:s:l:n:o:ih"
            longList = ["params=", "spaces=", "limits=", "num=", "output=", "int", "help"]
            opts, args = getopt.getopt(argv, shortList, longList)
        except getopt.GetoptError:
            self.usage()
            sys.exit(2)
        
        for opt, arg in opts:
            if opt in ("-h", "--help"):
                help()
            elif opt in ("-p", "--params"):
                params = self.getParams(arg)
            elif opt in ("-s", "--spaces"):
                spaces = self.getSpaces(arg)
            elif opt in ("-l", "--limits"):
                limits = self.getLimits(arg)
            elif opt in ("-n", "--num"):
                numPoints = self.getNumPoints(arg)
            elif opt in ("-o", "--output"):
                outFile = self.getOutFile(arg)
            elif opt in ("-i", "--int"):
                intValues = True
                    
        ignored = "".join(args)
        print("WARNING: Following input will be ignored: " + str(ignored) + ".\n")
                



class PythonLauncher:
    
    def __init__(self):
        self.gotError = False
    
    def launch(self, argv):
        self.gotError = False
        params = None 
        spaces = None
        limits = None
        numPoints = None
        outFile = None
        intValues = None
        
        print("\n*** dsgen - Data Set Generator ***")
        print("Please use dsgen -h or dsgen --help to see how to use dsgen.\n")
    
        params = self.getParams(input("params="))
        if params == None:
            self.gotError()
        spaces = self.getSpaces(input("spaces="))
        if spaces == None:
            self.gotError()
        limits = self.getLimits(input("limits="))
        if (limits == None) or not(self.areParamsAndLimitsConsistent(params, limits)):
            self.gotError()
        numPoints = self.getNumPoints(input("num="))
        if (numPoints == None) or not(self.areSpacesAndNumPointsConsistent(spaces, numPoints)):
            self.gotError()
        outFile = self.getOutFile(input("output="))
        if outFile == None:
            self.gotError()
        intValues = False
                           
        
        
        if self.gotError or (params == None) or (spaces == None) or (limits == None) or (numPoints == None):
            self.gotError()

        
        dsGen = DataSetGenerator(params, limits, spaces, numPoints, intValues, outFile)
        dsGen.generate()
        print("\ndsgen has finished generating your data set. Thanks.\n")
        
    def gotError(self):
        print("ERROR: dsgen stopped because of some INVALID INPUT PARAMETERS.")
        print("Please use dsgen -h or dsgen --help to see how to use dsgen.\n")
        sys.exit()
        
    def help(self):
        self.usage()
        sys.exit()
    
    def areSpacesAndNumPointsConsistent(self, spaces, numPoints):
        if (numPoints != None) and (spaces != None) and (type(numPoints) == list) and (len(spaces) != len(numPoints)):
            print("HINT: The number of points must be defined for each space.")
            return False
        return True
            
    def areParamsAndLimitsConsistent(self, params, limits):
        if (params != None) and (limits != None) and (len(params) != len(limits)):
            print("HINT: Min/max must be defined for each coordinate.")
            return False
        return True
    
    def getParams(self, arg):
        try:
            p = eval(arg)
        except Exception:
            self.incorrectParams(arg)
            return
        if type(p) != list:
            self.incorrectParams(arg)
            return
        for e in p:
            if type(e) != str:
                self.incorrectParams(arg)
                return
        return p
        
    def incorrectParams(self, arg):
        print("ERROR: Invalid value for -p/--params: " + str(arg))
        self.printRuleParams()
        _error = True
        
    def printRuleParams(self):
        print("-p, --params")
        print("\tAn array of string representing every coordinate used to describe points.")
        print("\tExample: [""x"",""y""].\n")
        
        
    def getSpaces(self, arg):
        try:
            space = eval(arg)
        except Exception:
            self.incorrectSpaces(arg)
            return
        if type(space) != dict:
            self.incorrectSpaces(arg)
            return
        for k,v in space.items():
            if type(k) != str:
                self.incorrectSpaces(arg)
                return
            if type(v) != str:
                self.incorrectSpaces(arg)
                return
        return space
        
    def incorrectSpaces(self, arg):
        print("ERROR: Invalid value for -s/--spaces: " + str(arg))
        self.printRuleSpaces()
        _error = True
        
    def printRuleSpaces(self):
        print("-s, --spaces")
        print("\tA dictionary of pairs <string, string> representing <label, space definition>.")
        print("\tExample: {""A"":""y>x**2"", ""B"":""y<x**2""}.\n")
        
    
    def getLimits(self, arg):
        try:
            limit = eval(arg)
        except Exception:
            self.incorrectLimits(arg)
            return
        if type(limit) != list:
            self.incorrectLimits(arg)
            return
        for elem in limit:
            if type(elem) != list:
                self.incorrectLimits(arg)
                return
            if len(elem) != 2:
                self.incorrectLimits(arg)
                print("HINT: For each element in -l/--limits, you must have 2 values, 1 min and 1 max.")
                return
            for value in elem:
                if type(value) != int and type(value) != float:
                    self.incorrectLimits(arg)
                    print("HINT: Min and max must be numerics for each element in -l/--limits.")
                    return
            if elem[0] >= elem[1]:
                self.incorrectLimits(arg)
                print("HINT: You must have (min < max) for each element in -l/--limits.")
                return
                
        return limit
        
    def incorrectLimits(self, arg):
        print("ERROR: Invalid value for -l/--limits: " + str(arg) + ".")
        self.printRuleLimits()
        _error = True
        
    def printRuleLimits(self):
        print("-l, --limits")
        print("\tAn array of pairs of numerics representing minimum and maximum for each coordinate: [[min1,max1], ..., [minN,maxN]].")
        print("\tExample: [[-2,2], [0,3.5]] assuming [""x"",""y""] for coordinates.\n")
        
    
    def getNumPoints(self, arg):
        try:
            numPoints = eval(arg)
        except Exception:
            self.incorrectNumPoints(arg)
            return
        
        argType = type(numPoints)
        if (argType != list) and (argType != int):
            print("HINT: The number of points to generate should be either an integer or an array of integers.")
            self.incorrectNumPoints(arg)
            return
        
        if (argType == int):
            if (numPoints > 0):
                return numPoints
            else: 
                self.incorrectNumPoints(arg)
                print("HINT: The number of points to generate must be strictly positive.")
                return
            
        if (argType == list):
            for elem in numPoints:
                if (type(elem) != int):
                    self.incorrectNumPoints(arg)
                    print("HINT: The number of points to generate for each space must be an integer.")
                    return
                if (elem <= 0):
                    self.incorrectNumPoints(arg)
                    print("HINT: The number of points to generate for each space must be strictly positive.")
                    return
    
        return numPoints
        
    def incorrectNumPoints(self, arg):
        print("ERROR: Invalid value for -n/--num: " + str(arg) + ".")
        self.printRuleNumPoints()
        _error = True
        
    def printRuleNumPoints(self):
        print("-n, --num")
        print("\tThe number of points to generate, overall or for each space.")
        print("\tExample #1: 1000. Will randomly generate 1000 points.")
        print("\tExample #2: [50, 50, 50]. Will randomly generate 50 points for each space, assuming 3 defined spaces.")
        print("\tExample #3: [200, 100, 100]. Will randomly generate 200 points for the first space, and 100 points for the two other ones (biased data set).\n")
    
    
    def getOutFile(self, arg):
        if type(arg) != str:
            self.incorrectOutFile(arg)
            return
        
        dirs = os.path.dirname(arg)
        if (self.wasAbleToCreateFile(dirs, arg)):
            return arg
        else:
            self.incorrectOutFile(arg)
            print("HINT: Check the correctness of the path and your rights on the folders.")
            return
    
    def wasAbleToCreateFile(self, dirs, path):
        result = True
        try:
            if (dirs != None) and (dirs != "") and (not os.path.exists(dirs)):
                os.makedirs(dirs)
            file = open(path, 'w')
            file.close()
        except Exception:
            result = False
        try:
            os.remove(path)
        except Exception:
            result = False
        return result
        
    def incorrectOutFile(self, arg):
        print("ERROR: Invalid value for -o/--output: " + str(arg) + ".")
        self.printRuleOutFile()
        _error = True
        
    def printRuleOutFile(self):
        print("-o, --output")
        print("\tPath to the file to create and to fill with the generated points. If not specified, standard output is used (print screen).\n")
    
    def printRuleIntValues(self):
        print("-i, --int")
        print("\tThe coordinates of the generated points will be integers only. Default is float.\n")
    
    
    def usage(self):      
        print("\n*** dsgen - Data Set Generator ***")
        print("Author: Marc CARRE <carre.marc@gmail.com>\n")  
        print("SYNOPSIS:")
        print("dsgen -p [...] -s {...} -l [...] [OPTIONS]\n")    
        print("DESCRIPTION:")
        print("Generate -n points that have coordinates as described by -p, subject to constraints (min/max) as in -l, and label them using the spaces description from -s.\n")
        print("MANDATORY ARGUMENTS:")
        self.printRuleParams()
        self.printRuleSpaces()
        self.printRuleLimits()
        self.printRuleNumPoints()
        print("OPTIONAL ARGUMENTS:")
        self.printRuleOutFile()
        self.printRuleIntValues()
        print("EXAMPLES:")
    

if __name__ == "__main__":
    PythonLauncher().launch(sys.argv[1:])
        